/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 
 * MA  02110-1301, USA.
 *
 * http://www.gnu.org/copyleft/gpl.html
 */
package com.aionemu.gameserver.model.team.group;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javolution.util.FastList;

import com.aionemu.commons.utils.Rnd;
import com.aionemu.gameserver.configs.main.GroupConfig;
import com.aionemu.gameserver.model.Race;
import com.aionemu.gameserver.model.gameobjects.Item;
import com.aionemu.gameserver.model.gameobjects.Npc;
import com.aionemu.gameserver.model.gameobjects.VisibleObject;
import com.aionemu.gameserver.model.gameobjects.player.Player;
import com.aionemu.gameserver.model.gameobjects.player.PlayerCommonData;
import com.aionemu.gameserver.model.gameobjects.player.RequestResponseHandler;
import com.aionemu.gameserver.model.team.Team;
import com.aionemu.gameserver.model.team.alliance.PlayerAlliance;
import com.aionemu.gameserver.model.team.interfaces.ITeamMember;
import com.aionemu.gameserver.network.aion.clientpackets.CM_PLAYER_STATUS_INFO.ePlayerStatus;
import com.aionemu.gameserver.network.aion.serverpackets.SM_GROUP_INFO;
import com.aionemu.gameserver.network.aion.serverpackets.SM_GROUP_MEMBER_INFO;
import com.aionemu.gameserver.network.aion.serverpackets.SM_LEAVE_GROUP_MEMBER;
import com.aionemu.gameserver.network.aion.serverpackets.SM_QUESTION_WINDOW;
import com.aionemu.gameserver.network.aion.serverpackets.SM_SYSTEM_MESSAGE;
import com.aionemu.gameserver.questEngine.QuestEngine;
import com.aionemu.gameserver.questEngine.model.QuestEnv;
import com.aionemu.gameserver.restrictions.RestrictionsManager;
import com.aionemu.gameserver.services.DropService;
import com.aionemu.gameserver.utils.MathUtil;
import com.aionemu.gameserver.utils.PacketSendUtility;
import com.aionemu.gameserver.utils.ThreadPoolManager;
import com.aionemu.gameserver.utils.idfactory.IDFactory;
import com.aionemu.gameserver.utils.stats.StatFunctions;
import com.aionemu.gameserver.world.WorldType;

/**
 * @author lyahim
 *
 */
public class PlayerGroup extends Team<PlayerCommonData> implements ITeamMember
{
	private LootGroupRules	lootGroupRules		= new LootGroupRules();
	private List<Integer>	inRangePlayers;
	private PlayerAlliance	playerAlliance		= null;
	private boolean			isAllianaceGroup	= false;

	public PlayerGroup(int teamId, Player leader)
	{
		super(teamId, leader.getObjectId());
		addMember(leader.getObjectTemplate());
		leader.setPlayerGroup(this);
	}

	public PlayerGroup(int teamId, Player leader, boolean isAllianaceGroup)
	{
		super(teamId, leader.getObjectId());
		addMember(leader.getObjectTemplate());
		leader.setPlayerGroup(this);
		this.isAllianaceGroup = isAllianaceGroup;
	}

	@Override
	public void addMember(PlayerCommonData member)
	{
		super.addMember(member);
		Player activePlayer = member.getPlayer();
		addOnlineMember(activePlayer);
		activePlayer.setPlayerGroup(this);
		updateGroupUIToEvent(member, GroupEvent.ENTER);
	}

	public void removePlayerFromGroup(int objectId)
	{
		PlayerCommonData member = members.get(objectId);
		updateGroupUIToEvent(member, GroupEvent.LEAVE);
		if (member == null)
			return;
		super.removeMember(objectId);
		if (member.isOnline())
			member.getPlayer().setPlayerGroup(null);
		if ((size() < 2 && playerAlliance == null) || size() == 0)
			disband();
	}

	public void updateGroupUIToEvent(PlayerCommonData subjective, GroupEvent groupEvent)
	{
		if (isAllianaceGroup)
		{
			if (this.playerAlliance != null)
				playerAlliance.updateAllianceUIToEvent(subjective, groupEvent);
			return;
		}
		switch (groupEvent)
		{
			case CHANGELEADER:
				PacketSendUtility.broadcastPacketToTeam(null, this, new SM_GROUP_INFO(this));
				if (subjective.isOnline())
					PacketSendUtility.sendPacket(subjective.getPlayer(), SM_SYSTEM_MESSAGE.CHANGE_GROUP_LEADER());
				PacketSendUtility.broadcastPacketToTeam(subjective.getPlayer(), this, new SM_GROUP_MEMBER_INFO(this, subjective, groupEvent));
				break;
			case LEAVE:
				boolean changeleader = false;
				if (subjective == this.getLeader())// change group leader
				{
					this.setLeader(this.getOnlineMembers().iterator().next().getObjectTemplate());
					changeleader = true;
				}
				if (changeleader)
				{
					PacketSendUtility.broadcastPacketToTeam(null, this, new SM_GROUP_INFO(this));
					PacketSendUtility.broadcastPacketToTeam(subjective.getPlayer(), this, SM_SYSTEM_MESSAGE.CHANGE_GROUP_LEADER());
				}
				PacketSendUtility.broadcastPacketToTeam(subjective.getPlayer(), this, new SM_GROUP_MEMBER_INFO(this, subjective, groupEvent));
				if (this.size() > 1)
					PacketSendUtility.broadcastPacketToTeam(subjective.getPlayer(), this, SM_SYSTEM_MESSAGE.MEMBER_LEFT_GROUP(subjective.getName()));
				if (subjective.isOnline())
				{
					PacketSendUtility.sendPacket(subjective.getPlayer(), SM_SYSTEM_MESSAGE.YOU_LEFT_GROUP());
					PacketSendUtility.sendPacket(subjective.getPlayer(), new SM_LEAVE_GROUP_MEMBER());
				}
				break;
			case ENTER:
				PacketSendUtility.sendPacket(subjective.getPlayer(), new SM_GROUP_INFO(this));
				PacketSendUtility.broadcastPacketToTeam(subjective.getPlayer(), this, new SM_GROUP_MEMBER_INFO(this, subjective, groupEvent));
				for (PlayerCommonData player : getMembers())
				{
					if (subjective.getObjectId() != player.getObjectId())
						PacketSendUtility.sendPacket(subjective.getPlayer(), new SM_GROUP_MEMBER_INFO(this, player, groupEvent));
				}
				break;
			case DISCONNECTED:
				PacketSendUtility.broadcastPacketToTeam(subjective.getPlayer(), this, new SM_GROUP_MEMBER_INFO(this, subjective, groupEvent));
				break;
			default:
			{
				PacketSendUtility.broadcastPacketToTeam(subjective.getPlayer(), this, new SM_GROUP_MEMBER_INFO(this, subjective, groupEvent));
			}
				break;
		}
	}

	public void setLootGroupRules(LootGroupRules lgr)
	{
		this.lootGroupRules = lgr;
		PacketSendUtility.broadcastPacketToTeam(null, this, new SM_GROUP_INFO(this));
	}

	/**
	 * @return the lootGroupRules
	 */
	public LootGroupRules getLootGroupRules()
	{
		return lootGroupRules;
	}

	@Override
	public boolean isFull()
	{
		return size() == 6;
	}

	@Override
	public String getName()
	{
		return "Player Group: " + getObjectId();
	}

	@Override
	public void getReward(Npc owner)
	{
		// Find Group Members and Determine Highest Level
		List<Player> players = new ArrayList<Player>();
		int partyLvlSum = 0;
		int highestLevel = 0;
		for (Player member : getOnlineMembers())
		{
			if (!member.isOnline())
				continue;
			if (MathUtil.isIn3dRange(member, owner, GroupConfig.GROUP_MAX_DISTANCE))
			{
				if (member.getLifeStats().isAlreadyDead())
					continue;
				players.add(member);
				partyLvlSum += member.getLevel();
				if (member.getLevel() > highestLevel)
					highestLevel = member.getLevel();
			}
		}

		// All are dead or not nearby.
		if (players.size() == 0)
			return;

		//AP reward
		int apRewardPerMember = 0;
		WorldType worldType = owner.getWorldType();

		//WorldType worldType = sp.getWorld().getWorldMap(player.getWorldId()).getWorldType();
		if (worldType == WorldType.ABYSS)
		{
			// Split Evenly
			apRewardPerMember = Math.round(StatFunctions.calculateGroupAPReward(highestLevel, owner) / players.size());
		}

		// Exp reward
		long expReward = StatFunctions.calculateGroupExperienceReward(highestLevel, owner);

		// Party Bonus 2 members 10%, 3 members 20% ... 6 members 50%
		int bonus = 1;
		if (players.size() == 0)
			return;
		else if (players.size() > 1)
			bonus = 100 + ((players.size() - 1) * 10);

	
			for (Player member : players)
			{
				if (players.size() == 1)
				{
					expReward = StatFunctions.calculateSoloExperienceReward(member, owner);
				}
				// Exp reward
				long currentExp = member.getObjectTemplate().getExp();
				long reward = (expReward * bonus * member.getLevel()) / (partyLvlSum * 100);
				reward *= member.getRates().getGroupXpRate();
				// Players 10 levels below highest member get 0 exp.
				if (highestLevel - member.getLevel() >= 10)
					reward = 0;
				member.getObjectTemplate().setExp(currentExp + reward);

				PacketSendUtility.sendPacket(member, SM_SYSTEM_MESSAGE.EXP(Long.toString(reward)));

				// DP reward
				int currentDp = member.getObjectTemplate().getDp();
				int dpReward = StatFunctions.calculateGroupDPReward(member, owner);
				member.getObjectTemplate().setDp(dpReward + currentDp);

				// AP reward
				if (apRewardPerMember > 0)
					member.getObjectTemplate().addAp(Math.round(apRewardPerMember * member.getRates().getApNpcRate()));

				QuestEngine.getInstance().onKill(new QuestEnv(owner, member, 0, 0));
			}
		// Give Drop
		setInRangePlayers(players);
		Player player = players.get(Rnd.get(players.size() - 1));
		DropService.getInstance().registerDrop(owner, player, highestLevel);
	}

	/**
	 * @param inRangePlayers the inRangePlayers to set
	 */
	private void setInRangePlayers(List<Player> inRangePlayers)
	{
		List<Integer> list = new FastList<Integer>();
		for (Player memb : inRangePlayers)
			list.add(memb.getObjectId());
		this.inRangePlayers = list;
	}

	/**
	 * @return the inRangePlayers
	 */
	public List<Integer> getInRangePlayers()
	{
		return inRangePlayers;
	}

	private void disband()
	{
		IDFactory.getInstance().releaseId(getObjectId());
		for (Player player : getOnlineMembers())
			if (player != null)
			{
				player.setPlayerGroup(null);
				PacketSendUtility.sendPacket(player, SM_SYSTEM_MESSAGE.DISBAND_GROUP());
			}
		deleteTeam();
	}

	@Override
	public void setLeader(PlayerCommonData newLeader)
	{
		super.setLeader(newLeader);
		updateGroupUIToEvent(getLeader(), GroupEvent.CHANGELEADER);
	}

	public void playerStatusInfo(ePlayerStatus status, PlayerCommonData player)
	{
		switch (status)
		{
			case GROUP_2:
				removePlayerFromGroup(player.getObjectId());
				break;
			case GROUP_3:
				setLeader(player);
				break;
			case GROUP_6:
				removePlayerFromGroup(player.getObjectId());
				break;
		}
	}

	public void scheduleRemove(final int objectId)
	{
		ThreadPoolManager.getInstance().schedule(new Runnable()
		{
			@Override
			public void run()
			{
				removePlayerFromGroup(objectId);
			}
		}, GroupConfig.GROUP_REMOVE_TIME * 1000);
		PacketSendUtility.broadcastPacketToTeam(null, this, SM_SYSTEM_MESSAGE.PARTY_HE_BECOME_OFFLINE(members.get(objectId).getName()));
	}

	public void groupDistribution(Player player, long amount)
	{
		long availableKinah = player.getInventory().getKinahItem().getItemCount();
		if (availableKinah < amount)
		{
			// TODO retail message ?
			return;
		}

		long rewardcount = size() - 1;
		if (rewardcount <= amount)
		{
			long reward = amount / rewardcount;

			for (Player groupMember : getOnlineMembers())
			{
				if (groupMember.equals(player))
					Item.decreaseKinah(groupMember, amount);
				else
					Item.increaseKinah(groupMember, reward);
			}
		}
	}

	public static void invitePlayerToGroup(final Player inviter, final Player invited)
	{
		if (RestrictionsManager.canInviteToGroup(inviter, invited))
		{
			RequestResponseHandler responseHandler = new RequestResponseHandler(inviter)
			{
				@Override
				public void acceptRequest(VisibleObject requester, Player responder)
				{
					PlayerGroup group = inviter.getPlayerGroup();
					if (group != null && group.isFull())
						return;

					PacketSendUtility.sendPacket(inviter, SM_SYSTEM_MESSAGE.REQUEST_GROUP_INVITE(invited.getName()));
					if (group != null)
					{
						group.addMember(invited.getObjectTemplate());
					}
					else
					{
						group = new PlayerGroup(IDFactory.getInstance().nextId(), inviter);
						group.addMember(invited.getObjectTemplate());
					}
				}

				@Override
				public void denyRequest(VisibleObject requester, Player responder)
				{
					PacketSendUtility.sendPacket(inviter, SM_SYSTEM_MESSAGE.REJECT_GROUP_INVITE(responder.getName()));
				}
			};

			boolean result = invited.getResponseRequester().putRequest(SM_QUESTION_WINDOW.STR_REQUEST_GROUP_INVITE, responseHandler);
			if (result)
			{
				PacketSendUtility.sendPacket(invited, new SM_QUESTION_WINDOW(SM_QUESTION_WINDOW.STR_REQUEST_GROUP_INVITE, 0, inviter.getName()));
			}
		}
	}

	public List<Integer> getMembersToRegistrateByRules()
	{
		List<Integer> luckyMembers = new ArrayList<Integer>();

		switch (getLootGroupRules().getLootRule())
		{
			case ROUNDROBIN:
			case FREEFORALL:
				luckyMembers.addAll(getInRangePlayers());
				break;
			case LEADER:
				luckyMembers.add(getLeader().getObjectId());
				break;
		}
		return luckyMembers;
	}

	public PlayerAlliance getPlayerAlliance()
	{
		return playerAlliance;
	}

	public void setPlayerAlliance(PlayerAlliance playerAlliance)
	{
		this.playerAlliance = playerAlliance;
		isAllianaceGroup = playerAlliance != null;
	}

	@Override
	public Collection<Player> getOnlineMembers()
	{
		return onlineMembers.values();
	}

	@Override
	public PlayerCommonData getLeader()
	{
		return members.get(leader);
	}

	@Override
	public PlayerCommonData getMemberById(int id)
	{
		return members.get(id);
	}

	@Override
	public void onMemberLogOut(int memberId)
	{
		scheduleRemove(memberId);
		super.onMemberLogOut(memberId);
	}

	@Override
	public void onMemberLogOut(PlayerCommonData member)
	{
		onMemberLogOut(member.getObjectId());
		updateGroupUIToEvent(member, GroupEvent.DISCONNECTED);
		member.getPlayer().setPlayerGroup(null);
	}

	@Override
	public void onMemberLogIn(int objectId)
	{
		PlayerCommonData member = members.get(objectId);
		Player activePlayer = member.getPlayer();
		activePlayer.setPlayerGroup(this);
		addOnlineMember(activePlayer);
		updateGroupUIToEvent(member, GroupEvent.ENTER);
	}

	@Override
	public Race getRace()
	{
		return getLeader().getRace();
	}
}
